﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/9 10:35:35
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Java;
using ImageK.Process;

namespace ImageK.IO
{
    /// <summary>
    /// Reads raw 8-bit, 16-bit or 32-bit (float or RGB)
    /// images from a stream or URL.
    /// </summary>
    public class ImageReader
    {
        private const int CLEAR_CODE = 256;
        private const int EOI_CODE = 257;

        private FileInfo _fi;
        private int _width, _height;
        private long _skipCount;
        private int bytesPerPixel, _bufferSize, nPixels;
        private long _byteCount;
        private bool _showProgressBar = true;
        private int _eofErrorCount;
        private int imageCount;
        private long startTime;
        public double min, max; // readRGB48() calculates min/max pixel values

        /**
        Constructs a new ImageReader using a FileInfo object to describe the file to be read.
        @see ij.io.FileInfo
        */
        public ImageReader(FileInfo fi)
        {
            this._fi = fi;
            _width = fi.width;
            _height = fi.height;
            _skipCount = fi.getOffset();
        }

        void eofError()
        {
            _eofErrorCount++;
        }

        /** 
        Skips the specified number of bytes, then reads an image and 
        returns the pixel array (byte, short, int or float). Returns
        null if there was an IO exception. Does not close the InputStream.
        */
        public object readPixels(InputStream rs, long skipCount)
        {
            this._skipCount = skipCount;
            _showProgressBar = false;
            object pixels = readPixels(rs);
            if (_eofErrorCount > (imageCount == 1 ? 1 : 0))
                return null;
            else
                return pixels;
        }

        void skip(InputStream rs)
        {
            if (_skipCount > 0)
            {
                long bytesRead = 0;
                int skipAttempts = 0;
                long count;
                while (bytesRead < _skipCount)
                {
                    count = rs.skip(_skipCount - bytesRead);
                    skipAttempts++;
                    if (count == -1 || skipAttempts > 5) break;
                    bytesRead += count;
                }
            }
            _byteCount = ((long)_width) * _height * bytesPerPixel;
            if (_fi.fileType == FileInfo.BITMAP)
            {
                int scan = _width / 8, pad = _width % 8;
                if (pad > 0) scan++;
                _byteCount = scan * _height;
            }
            nPixels = _width * _height;
            _bufferSize = (int)(_byteCount / 25L);
            if (_bufferSize < 8192)
                _bufferSize = 8192;
            else
                _bufferSize = (_bufferSize / 8192) * 8192;
		}
		/** 
        Reads the image from the InputStream and returns the pixel
        array (byte, short, int or float). Returns null if there
        was an IO exception. Does not close the InputStream.
        */
		public object readPixels(InputStream rs)
		{
			object pixels;
            startTime = DateTime.Now.Ticks;
			try
			{
				switch (_fi.fileType)
				{
					case FileInfo.GRAY8:
					case FileInfo.COLOR8:
						bytesPerPixel = 1;
						skip(rs);
						pixels = (object)read8bitImage(rs);
						break;
					case FileInfo.GRAY16_SIGNED:
					case FileInfo.GRAY16_UNSIGNED:
						bytesPerPixel = 2;
						skip(rs);
						pixels = (object)read16bitImage(rs);
						break;
					//todo:
					case FileInfo.GRAY32_INT:
					case FileInfo.GRAY32_UNSIGNED:
					case FileInfo.GRAY32_FLOAT:
						bytesPerPixel = 4;
						skip(rs);
						pixels = (object)read32bitImage(rs);
						break;
					// case FileInfo.GRAY64_FLOAT:
					// 	bytesPerPixel = 8;
					// 	skip(rs);
					// 	pixels = (object)read64bitImage(rs);
					// 	break;
					case FileInfo.RGB:
					case FileInfo.BGR:
					case FileInfo.ARGB:
					case FileInfo.ABGR:
					case FileInfo.BARG:
					case FileInfo.CMYK:
						bytesPerPixel = _fi.getBytesPerPixel();
						skip(rs);
						pixels = (object)readChunkyRGB(rs);
						break;
					// case FileInfo.RGB_PLANAR:
					// 	if (!(rs instanceof RandomAccessStream) && _fi.stripOffsets != null && _fi.stripOffsets.Length > 1)
					// 	rs = new RandomAccessStream(rs);
					// 	bytesPerPixel = 3;
					// 	skip(in);
					// 	pixels = (object)readPlanarRGB(in);
					// 	break;
					// case FileInfo.BITMAP:
					// 	bytesPerPixel = 1;
					// 	skip(in);
					// 	pixels = (object)read1bitImage(in);
					// 	break;
					// case FileInfo.RGB48:
					// 	bytesPerPixel = 6;
					// 	skip(in);
					// 	pixels = (object)readRGB48(in);
					// 	break;
					// case FileInfo.RGB48_PLANAR:
					// 	bytesPerPixel = 2;
					// 	skip(in);
					// 	pixels = (object)readRGB48Planar(in);
					// 	break;
					// case FileInfo.GRAY12_UNSIGNED:
					// 	skip(in);
					// 	short[] data = read12bitImage(in);
					// 	pixels = (object)data;
					// 	break;
					// case FileInfo.GRAY24_UNSIGNED:
					// 	skip(in);
					// 	pixels = (object)read24bitImage(in);
					// 	break;
					default:
						pixels = null;
                        break;
				}
				showProgress(1, 1);
				imageCount++;
				return pixels;
			}
			catch (IOException e)
			{
				IJ.log("" + e);
				return null;
			}
		}


        byte[] read8bitImage(InputStream rs)
        {
            if (_fi.compression>FileInfo.COMPRESSION_NONE)
                return readCompressed8bitImage(rs);
            byte[] pixels = new byte[nPixels];
            // assume contiguous strips
            int count, actuallyRead;
            int totalRead = 0;
            while (totalRead<_byteCount) {
                if (totalRead + _bufferSize>_byteCount)
                    count = (int) (_byteCount-totalRead);
                else
                    count = _bufferSize;
                actuallyRead = rs.Read(pixels, totalRead, count);
                if (actuallyRead==-1) {eofError(); break;}
                totalRead += actuallyRead;
                showProgress(totalRead, _byteCount);
            }
            return pixels;
        }

        byte[] readCompressed8bitImage(InputStream rs)
        {

            byte[] pixels = new byte[nPixels];
            int current = 0;
            byte last = 0;
            for (int i=0; i<_fi.stripOffsets.Length; i++) {
                if (rs is RandomAccessStream)
                    ((RandomAccessStream)rs).Seek(_fi.stripOffsets[i]);
                else if (i > 0) {
                    long skip = (_fi.stripOffsets[i]&0xffffffffL) - (_fi.stripOffsets[i-1]&0xffffffffL) - _fi.stripLengths[i-1];
                    if (skip > 0L) rs.skip(skip);
                }
                byte[] byteArray = new byte[_fi.stripLengths[i]];
                int read = 0, left = byteArray.Length;
                while (left > 0) {
                    int r = rs.Read(byteArray, read, left);
                    if (r == -1) {eofError(); break;}
                    read += r;
                    left -= r;
                }
                byteArray = uncompress(byteArray);
                int length = byteArray.Length;
                length = length - (length%_fi.width);
                if (_fi.compression==FileInfo.LZW_WITH_DIFFERENCING||_fi.compression==FileInfo.ZIP_WITH_DIFFERENCING)
                {
                    for (int b = 0; b<length; b++)
                    {
                        byteArray[b] += last;
                        last = b % _fi.width == _fi.width - 1 ? (byte)0 : byteArray[b];
                    }
                }
                if (current+length>pixels.Length)
                    length = pixels.Length-current;
                Array.Copy(byteArray, 0, pixels, current, length);
                current += length;
                showProgress(i+1, _fi.stripOffsets.Length);
            }
            return pixels;
        }

		/// <summary>
		/// Reads a 16-bit image. Signed pixels are converted to unsigned by adding 32768.
		/// </summary>
		/// <param name=""></param>
		/// <param name=""></param>
		/// <returns></returns>
		short[] read16bitImage(InputStream rs)
        {
            if (_fi.compression > FileInfo.COMPRESSION_NONE || (_fi.stripOffsets != null && _fi.stripOffsets.Length > 1) && _fi.fileType != FileInfo.RGB48_PLANAR)
                return readCompressed16bitImage(rs);
            int pixelsRead;
            byte[] buffer = new byte[_bufferSize];
            short[] pixels = new short[nPixels];
            long totalRead = 0L;
            int bas = 0;
            int count, value;
            int bufferCount;

            while (totalRead < _byteCount)
            {
                if ((totalRead + _bufferSize) > _byteCount)
                    _bufferSize = (int)(_byteCount - totalRead);
                bufferCount = 0;
                while (bufferCount < _bufferSize)
                { // fill the buffer
                    count = rs.Read(buffer, bufferCount, _bufferSize - bufferCount);
                    if (count == -1)
                    {
                        for (int i = bufferCount; i < _bufferSize; i++) buffer[i] = 0;
                        totalRead = _byteCount;
                        eofError();
                        break;
                    }
                    bufferCount += count;
                }
                totalRead += _bufferSize;
                showProgress(totalRead, _byteCount);
                pixelsRead = _bufferSize / bytesPerPixel;
                if (_fi.intelByteOrder)
                {
                    if (_fi.fileType == FileInfo.GRAY16_SIGNED)
                        for (int i = bas, j = 0; i < (bas + pixelsRead); i++, j += 2)
                            pixels[i] = (short)((((buffer[j + 1] & 0xff) << 8) | (buffer[j] & 0xff)) + 32768);
                    else
                        for (int i = bas, j = 0; i < (bas + pixelsRead); i++, j += 2)
                            pixels[i] = (short)(((buffer[j + 1] & 0xff) << 8) | (buffer[j] & 0xff));
                }
                else
                {
                    if (_fi.fileType == FileInfo.GRAY16_SIGNED)
                        for (int i = bas, j = 0; i < (bas + pixelsRead); i++, j += 2)
                            pixels[i] = (short)((((buffer[j] & 0xff) << 8) | (buffer[j + 1] & 0xff)) + 32768);
                    else
                        for (int i = bas, j = 0; i < (bas + pixelsRead); i++, j += 2)
                            pixels[i] = (short)(((buffer[j] & 0xff) << 8) | (buffer[j + 1] & 0xff));
                }
                bas += pixelsRead;
            }
            return pixels;
		}

        short[] readCompressed16bitImage(InputStream rs)
        {
            if (IJ.debugMode) IJ.log("ImageReader.read16bit, offset=" + _fi.stripOffsets[0]);
            short[] pixels = new short[nPixels];
            int bas = 0;
            short last = 0;
            for (int k = 0; k < _fi.stripOffsets.Length; k++)
            {
                //IJ.log("seek: "+k+" "+fi.stripOffsets[k]+" "+fi.stripLengths[k]+"  "+(in instanceof RandomAccessStream));
                if (rs is RandomAccessStream)
                    ((RandomAccessStream)rs).Seek(_fi.stripOffsets[k]);
                else if (k > 0)
                {
                    long skip = (_fi.stripOffsets[k] & 0xffffffffL) - (_fi.stripOffsets[k - 1] & 0xffffffffL) - _fi.stripLengths[k - 1];
                    if (skip > 0L) rs.skip(skip);
                }
                byte[] byteArray = new byte[_fi.stripLengths[k]];
                int read = 0, left = byteArray.Length;
                while (left > 0)
                {
                    int r = rs.Read(byteArray, read, left);
                    if (r == -1) { eofError(); break; }
                    read += r;
                    left -= r;
                }
                byteArray = uncompress(byteArray);
                int pixelsRead = byteArray.Length / bytesPerPixel;
                pixelsRead = pixelsRead - (pixelsRead % _fi.width);
                int pmax = bas + pixelsRead;
                if (pmax > nPixels) pmax = nPixels;
                if (_fi.intelByteOrder)
                {
                    for (int i = bas, j = 0; i < pmax; i++, j += 2)
                        pixels[i] = (short)(((byteArray[j + 1] & 0xff) << 8) | (byteArray[j] & 0xff));
                }
                else
                {
                    for (int i = bas, j = 0; i < pmax; i++, j += 2)
                        pixels[i] = (short)(((byteArray[j] & 0xff) << 8) | (byteArray[j + 1] & 0xff));
                }
                if (_fi.compression == FileInfo.LZW_WITH_DIFFERENCING || _fi.compression == FileInfo.ZIP_WITH_DIFFERENCING)
                {
                    for (int b = bas; b < pmax; b++)
                    {
                        pixels[b] += last;
                        last = b % _fi.width == _fi.width - 1 ? (short)0 : pixels[b];
                    }
                }
                bas += pixelsRead;
                showProgress(k + 1, _fi.stripOffsets.Length);
            }
            if (_fi.fileType==FileInfo.GRAY16_SIGNED) {
                // convert to unsigned
                for (int i=0; i<nPixels; i++)
                    pixels[i] = (short) (pixels[i]+32768);
            }
            return pixels;
        }

        float[] read32bitImage(InputStream rs)
        {
            if (_fi.compression>FileInfo.COMPRESSION_NONE || (_fi.stripOffsets!=null&& _fi.stripOffsets.Length>1))
                return readCompressed32bitImage(rs);
            int pixelsRead;
            byte[] buffer = new byte[_bufferSize];
            float[] pixels = new float[nPixels];
            long totalRead = 0L;
            int bas = 0;
            int count, value;
            int bufferCount;
            int tmp;
		    
            while (totalRead<_byteCount) 
            {
                if ((totalRead+_bufferSize)>_byteCount)
                    _bufferSize = (int) (_byteCount-totalRead);
                bufferCount = 0;
                while (bufferCount<_bufferSize) { // fill the buffer
                    count = rs.Read(buffer, bufferCount, _bufferSize-bufferCount);
                    if (count==-1) {
                        for (int i=bufferCount; i<_bufferSize; i++) buffer[i] = 0;
                        totalRead = _byteCount;
                        eofError();
                        break;
                    }
                    bufferCount += count;
                }
                totalRead += _bufferSize;
                showProgress(totalRead, _byteCount);
                pixelsRead = _bufferSize / bytesPerPixel;
                int pmax = bas + pixelsRead;
                if (pmax > nPixels) pmax = nPixels;
                int j = 0;
                if (_fi.intelByteOrder)
                    for (int i = bas; i < pmax; i++)
                    {
                        tmp = (int)(((buffer[j + 3] & 0xff) << 24) | ((buffer[j + 2] & 0xff) << 16) | ((buffer[j + 1] & 0xff) << 8) | (buffer[j] & 0xff));
                        if (_fi.fileType == FileInfo.GRAY32_FLOAT)
                            pixels[i] = BitConverter.Int32BitsToSingle(tmp);
                        else if (_fi.fileType == FileInfo.GRAY32_UNSIGNED)
                            pixels[i] = (float)(tmp & 0xffffffffL);
                        else
                            pixels[i] = tmp;
                        j += 4;
                    }
                else
                    for (int i = bas; i < pmax; i++)
                    {
                        tmp = (int)(((buffer[j] & 0xff) << 24) | ((buffer[j + 1] & 0xff) << 16) | ((buffer[j + 2] & 0xff) << 8) | (buffer[j + 3] & 0xff));
                        if (_fi.fileType == FileInfo.GRAY32_FLOAT)
                            pixels[i] = BitConverter.Int32BitsToSingle(tmp);
                        else if (_fi.fileType == FileInfo.GRAY32_UNSIGNED)
                            pixels[i] = (float)(tmp & 0xffffffffL);
                        else
                            pixels[i] = tmp;
                        j += 4;
                    }
                bas += pixelsRead;
            }
            return pixels;
        }

        float[] readCompressed32bitImage(InputStream rs)
        {
            //todo：
            return new float[1];
        }


        int[] readChunkyRGB(InputStream rs)
        {
		    if (_fi.compression==FileInfo.JPEG)
			    return readJPEG(rs);
		    else if (_fi.compression>FileInfo.COMPRESSION_NONE || (_fi.stripOffsets!=null&&_fi.stripOffsets.Length>1))
			    return readCompressedChunkyRGB(rs);
            int pixelsRead;
            _bufferSize = 24*_width;
		    byte[] buffer = new byte[_bufferSize];
            int[] pixels = new int[nPixels];
            long totalRead = 0L;
            int bas = 0;
		    int count, value;
            int bufferCount;
            int r, g, b, a;
		    
		    while (totalRead<_byteCount) {
			    if ((totalRead+_bufferSize)>_byteCount)
				    _bufferSize = (int) (_byteCount-totalRead);
			    bufferCount = 0;
			    while (bufferCount<_bufferSize) { // fill the buffer
				    count = rs.Read(buffer, bufferCount, _bufferSize-bufferCount);
				    if (count==-1) {
					    for (int i=bufferCount; i<_bufferSize; i++) buffer[i] = 0;
					    totalRead = _byteCount;
					    eofError();
					    break;
				    }
                    bufferCount += count;
			    }
                totalRead += _bufferSize;
                showProgress(totalRead, _byteCount);
                pixelsRead = _bufferSize / bytesPerPixel;
                bool bgr = _fi.fileType == FileInfo.BGR;
                int j = 0;
                for (int i = bas; i < (bas + pixelsRead); i++)
                {
                    if (bytesPerPixel == 4)
                    {
                        if (_fi.fileType == FileInfo.BARG)
                        {  // MCID
                            b = buffer[j++] & 0xff;
                            j++; // ignore alfa byte
                            r = buffer[j++] & 0xff;
                            g = buffer[j++] & 0xff;
                        }
                        else if (_fi.fileType == FileInfo.ABGR)
                        {
                            b = buffer[j++] & 0xff;
                            g = buffer[j++] & 0xff;
                            r = buffer[j++] & 0xff;
                            j++; // ignore alfa byte
                        }
                        else if (_fi.fileType == FileInfo.CMYK)
                        {
                            r = buffer[j++] & 0xff; // c
                            g = buffer[j++] & 0xff; // m
                            b = buffer[j++] & 0xff; // y
                            a = buffer[j++] & 0xff; // k
                            if (a > 0)
                            { // if k>0 then  c=c*(1-k)+k
                                r = ((r * (256 - a)) >> 8) + a;
                                g = ((g * (256 - a)) >> 8) + a;
                                b = ((b * (256 - a)) >> 8) + a;
                            } // else  r=1-c, g=1-m and b=1-y, which IJ does by inverting image
                        }
                        else
                        { // ARGB
                            r = buffer[j++] & 0xff;
                            g = buffer[j++] & 0xff;
                            b = buffer[j++] & 0xff;
                            j++; // ignore alfa byte
                        }
                    }
                    else
                    {
                        r = buffer[j++] & 0xff;
                        g = buffer[j++] & 0xff;
                        b = buffer[j++] & 0xff;
                    }
                    if (bgr)
                        pixels[i] = (int)(0xff000000 | (b << 16) | (g << 8) | r);
                    else
                        pixels[i] = (int)(0xff000000 | (r << 16) | (g << 8) | b);
                }
                bas += pixelsRead;
		    }
		    return pixels;
	    }

        int[] readCompressedChunkyRGB(InputStream rs)
        {
            int[] pixels = new int[nPixels];
            int bas = 0;
            int lastRed = 0, lastGreen = 0, lastBlue = 0;
            int nextByte;
            int red = 0, green = 0, blue = 0, alpha = 0;
            bool bgr = _fi.fileType == FileInfo.BGR;
            bool cmyk = _fi.fileType == FileInfo.CMYK;
            bool differencing = _fi.compression == FileInfo.LZW_WITH_DIFFERENCING || _fi.compression == FileInfo.ZIP_WITH_DIFFERENCING;
            for (int i = 0; i < _fi.stripOffsets.Length; i++)
            {
                if (rs is RandomAccessStream)
				    ((RandomAccessStream)rs).Seek(_fi.stripOffsets[i]);
                else if (i > 0)
                {
                    long skip = (_fi.stripOffsets[i] & 0xffffffffL) - (_fi.stripOffsets[i - 1] & 0xffffffffL) - _fi.stripLengths[i - 1];
                    if (skip > 0L) rs.skip(skip);
                }
                byte[] byteArray = new byte[_fi.stripLengths[i]];
                int read = 0, left = byteArray.Length;
                while (left > 0)
                {
                    int r = rs.Read(byteArray, read, left);
                    if (r == -1) { eofError(); break; }
                    read += r;
                    left -= r;
                }
                byteArray = uncompress(byteArray);
                if (differencing)
                {
                    for (int b = 0; b < byteArray.Length; b++)
                    {
                        if (b / bytesPerPixel % _fi.width == 0) continue;
                        byteArray[b] += byteArray[b - bytesPerPixel];
                    }
                }
                int k = 0;
                int pixelsRead = byteArray.Length / bytesPerPixel;
                pixelsRead = pixelsRead - (pixelsRead % _fi.width);
                int pmax = bas + pixelsRead;
                if (pmax > nPixels) pmax = nPixels;
                for (int j = bas; j < pmax; j++)
                {
                    if (bytesPerPixel == 4)
                    {
                        red = byteArray[k++] & 0xff;
                        green = byteArray[k++] & 0xff;
                        blue = byteArray[k++] & 0xff;
                        alpha = byteArray[k++] & 0xff;
                        if (cmyk && alpha > 0)
                        {
                            red = ((red * (256 - alpha)) >> 8) + alpha;
                            green = ((green * (256 - alpha)) >> 8) + alpha;
                            blue = ((blue * (256 - alpha)) >> 8) + alpha;
                        }
                    }
                    else
                    {
                        red = byteArray[k++] & 0xff;
                        green = byteArray[k++] & 0xff;
                        blue = byteArray[k++] & 0xff;
                    }
                    if (bgr)
                        pixels[j] = (int)(0xff000000 | (blue << 16) | (green << 8) | red);
                    else
                        pixels[j] = (int)(0xff000000 | (red << 16) | (green << 8) | blue);
                }
                bas += pixelsRead;
                showProgress(i + 1, _fi.stripOffsets.Length);
            }
		    return pixels;
        }

        int[] readJPEG(InputStream rs)
        {
            throw new NotImplementedException();
        }

        private void showProgress(int current, int last)
        {
            if (_showProgressBar && (DateTime.Now.Ticks - startTime) > 500L)
                IJ.showProgress(current, last);
        }

        private void showProgress(long current, long last)
        {
            showProgress((int)(current / 10L), (int)(last / 10L));
        }


        private byte[] uncompress(byte[] input)
        {
            if (_fi.compression == FileInfo.PACK_BITS)
                return packBitsUncompress(input, _fi.rowsPerStrip * _fi.width * _fi.getBytesPerPixel());
            else if (_fi.compression == FileInfo.LZW || _fi.compression == FileInfo.LZW_WITH_DIFFERENCING)
                return lzwUncompress(input);
            else if (_fi.compression == FileInfo.ZIP || _fi.compression == FileInfo.ZIP_WITH_DIFFERENCING)
                return zipUncompress(input);
            else
                return input;
        }

        /** TIFF Adobe ZIP support contributed by Jason Newton. */
        public byte[] zipUncompress(byte[] input)
        {
            throw new NotImplementedException();
            // ByteArrayOutputStream imageBuffer = new ByteArrayOutputStream();
            // byte[] buffer = new byte[1024];
            // Inflater decompressor = new Inflater();
            // decompressor.setInput(input);
            // try
            // {
            //     while (!decompressor.finished())
            //     {
            //         int rlen = decompressor.inflate(buffer);
            //         imageBuffer.write(buffer, 0, rlen);
            //     }
            // }
            // catch (Exception e)
            // {
            //     IJ.log(e.toString());
            // }
            // decompressor.end();
            // return imageBuffer.toByteArray();
        }

        /**
        * Utility method for decoding an LZW-compressed image strip. 
        * Adapted from the TIFF 6.0 Specification:
        * http://partners.adobe.com/asn/developer/pdfs/tn/TIFF6.pdf (page 61)
        * Author: Curtis Rueden (ctrueden at wisc.edu)
        */
        public byte[] lzwUncompress(byte[] input)
        {
            if (input == null || input.Length == 0)
                return input;
            byte[][] symbolTable = new byte[16384][];
            for (int i = 0; i < 16384; i++)
            {
                symbolTable[i] = new byte[1];
            }
            int bitsToRead = 9;
            int nextSymbol = 258;
            int code;
            int oldCode = -1;
            ByteVector ot = new ByteVector(8192);
            BitBuffer bb = new BitBuffer(input);
            byte[] byteBuffer1 = new byte[16];
            byte[] byteBuffer2 = new byte[16];

            //todo:
            while (ot.size() < _byteCount) {
                code = bb.getBits(bitsToRead);
                if (code == EOI_CODE || code == -1)
                    break;
                if (code == CLEAR_CODE)
                {
                    // initialize symbol table
                    for (int i = 0; i < 256; i++)
                        symbolTable[i][0] = (byte)i;
                    nextSymbol = 258;
                    bitsToRead = 9;
                    code = bb.getBits(bitsToRead);
                    if (code == EOI_CODE || code == -1)
                        break;
				    ot.add(symbolTable[code]);
                    oldCode = code;
                }
                else
                {
                    if (oldCode == -1) oldCode = 0;
                    if (code < nextSymbol)
                    {
					    // code is in table
					    ot.add(symbolTable[code]);
                        // add string to table
                        ByteVector symbol = new ByteVector(byteBuffer1);
                        symbol.add(symbolTable[oldCode]);
                        symbol.add(symbolTable[code][0]);
                        symbolTable[nextSymbol] = symbol.toByteArray(); //**
                        oldCode = code;
                        nextSymbol++;
                    }
                    else
                    {
                        // out of table
                        ByteVector symbol = new ByteVector(byteBuffer2);
                        symbol.add(symbolTable[oldCode]);
                        symbol.add(symbolTable[oldCode][0]);
                        byte[] outString = symbol.toByteArray();
					    ot.add(outString);
                        symbolTable[nextSymbol] = outString; //**
                        oldCode = code;
                        nextSymbol++;
                    }
                    if (nextSymbol == 511) { bitsToRead = 10; }
                    if (nextSymbol == 1023) { bitsToRead = 11; }
                    if (nextSymbol == 2047) { bitsToRead = 12; }
                }
            }
            return ot.toByteArray();
        }


        /** Based on the Bio-Formats PackbitsCodec written by Melissa Linkert. */
        public byte[] packBitsUncompress(byte[] input, int expected)
        {
            if (expected == 0) expected = int.MaxValue;
            ByteVector output = new ByteVector(1024);
            int index = 0;
            while (output.size() < expected && index < input.Length)
            {
                byte n = input[index++];
                if (n >= 0)
                { // 0 <= n <= 127
                    byte[] b = new byte[n + 1];
                    for (int i = 0; i < n + 1; i++)
                        b[i] = input[index++];
                    output.add(b);
                    b = null;
                }
                else if (n != -128)
                { // -127 <= n <= -1
                    int len = -n + 1;
                    byte inp = input[index++];
                    for (int i = 0; i < len; i++) output.add(inp);
                }
            }
            return output.toByteArray();
        }
    }

    /** A growable array of bytes. */
    class ByteVector
    {
        private byte[] data;
        private int _size;

        public ByteVector()
        {
            data = new byte[10];
            _size = 0;
        }

        public ByteVector(int initialSize)
        {
            data = new byte[initialSize];
            _size = 0;
        }

        public ByteVector(byte[] byteBuffer)
        {
            data = byteBuffer;
            _size = 0;
        }

        public void add(byte x)
        {
            if (_size >= data.Length)
            {
                doubleCapacity();
                add(x);
            }
            else
                data[_size++] = x;
        }

        public int size()
        {
            return _size;
        }

        public void add(byte[] array)
        {
            int length = array.Length;
            while (data.Length - _size < length)
                doubleCapacity();
            Array.Copy(array, 0, data, _size, length);
            _size += length;
        }

        void doubleCapacity()
        {
            byte[] tmp = new byte[data.Length * 2 + 1];
            Array.Copy(data, 0, tmp, 0, data.Length);
            data = tmp;
        }

        public void clear()
        {
            _size = 0;
        }

        public byte[] toByteArray()
        {
            byte[] bytes = new byte[_size];
            Array.Copy(data, 0, bytes, 0, _size);
            return bytes;
        }

    }
}
